package com.quectel.core.module.schedulingapply.impl;


import cn.hutool.core.lang.func.LambdaUtil;
import com.quectel.constant.core.dept.DeptPersonConstants;
import com.quectel.constant.core.scheduling.SchedulingApplyConstants;
import com.quectel.constant.core.scheduling.SchedulingAuditPipelineSeqConstants;
import com.quectel.constant.global.SystemConstants;
import com.quectel.util.aspect.lock.annotation.Lock;
import com.quectel.core.constants.RedisCacheConstants;
import com.quectel.core.constants.RedisLockConstants;
import com.quectel.core.ex.schedulingapply.SchedulingApplyAuditException;
import com.quectel.core.module.dept.dto.DeptPersonDto;
import com.quectel.core.module.dept.service.DeptPersonService;
import com.quectel.core.module.scheduling.dto.*;
import com.quectel.core.module.scheduling.service.*;
import com.quectel.core.module.schedulingapply.dao.SchedulingApplyRecordDao;
import com.quectel.core.module.schedulingapply.dto.*;
import com.quectel.core.module.schedulingapply.dto.vo.SchedulingApplyAuditRsp;
import com.quectel.core.module.schedulingapply.dto.vo.SchedulingApplyCountVO;
import com.quectel.core.module.schedulingapply.dto.vo.SchedulingApplyRecordStatisticsVo;
import com.quectel.core.module.schedulingapply.dto.vo.SchedulingBalanceTimeVO;
import com.quectel.core.module.schedulingapply.entity.SchedulingApplyRecordEntity;
import com.quectel.core.module.schedulingapply.service.*;
import com.quectel.core.module.village.dto.VillageDto;
import com.quectel.core.module.village.service.VillageService;
import com.quectel.util.common.CopyUtils;
import com.quectel.util.common.DateUtils;
import com.quectel.util.common.JacksonUtils;
import com.quectel.util.ex.CheckErrorException;
import com.quectel.util.kit.CacheKit;
import com.quectel.util.kit.Snowflake;
import com.quectel.util.mybatis.MyBatisPlusUtils;
import com.quectel.util.spring.SpringUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.dubbo.config.annotation.DubboService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author zhangheng
 * @email nolan.zhang@quectel.com
 * @date 2023/07/13 19:14
 */
@DubboService
public class SchedulingApplyRecordServiceImpl implements SchedulingApplyRecordService {

    private static final Logger LOGGER = LoggerFactory.getLogger(SchedulingApplyRecordServiceImpl.class);

    /**
     * 入参解释:实体id
     */
    private static final Function<Long, String> SCHEDULING_APPLY_RECORD_CACHE_BY_ID_FUNC = id -> String.format(RedisCacheConstants.PROJECT_NAME + "SCHEDULING_APPLY_RECORD_CACHE_BY_ID:%d", id);


    /**
     * 清除缓存方法
     */
    private static final Consumer<SchedulingApplyRecordDto> CLEAR_CACHE_FUNC = dto -> {
        CacheKit.invalidRedisCache(SCHEDULING_APPLY_RECORD_CACHE_BY_ID_FUNC.apply(dto.getId()));
    };

    @Autowired
    private SchedulingApplyRecordDao schedulingApplyRecordDao;
    @Autowired
    private SchedulingApplyRecordAuditPersonService schedulingApplyRecordAuditPersonService;
    @Autowired
    private SchedulingAuditPipelineService schedulingAuditPipelineService;
    @Autowired
    private SchedulingAuditPipelineSeqDeptPersonService schedulingAuditPipelineSeqDeptPersonService;
    @Autowired
    private DeptPersonService deptPersonService;
    @Autowired
    private VillageService villageService;

    @Autowired
    private SchedulingApplyRecordAuditLogService schedulingApplyRecordAuditLogService;
    @Autowired
    private SchedulingLeaveConfigService schedulingLeaveConfigService;

    @Autowired
    private SchedulingApplyLeaveRecordService schedulingApplyLeaveRecordService;
    @Autowired
    private SchedulingApplyResumptionLeaveRecordService schedulingApplyResumptionLeaveRecordService;
    @Autowired
    private SchedulingApplyRenewCardRecordService schedulingApplyRenewCardRecordService;
    @Autowired
    private SchedulingApplyOvertimeRecordService schedulingApplyOvertimeRecordService;
    @Autowired
    private SchedulingApplyOutRecordService schedulingApplyOutRecordService;
    @Autowired
    private SchedulingApplyTripRecordService schedulingApplyTripRecordService;
    @Autowired
    private SchedulingApplyTransferClassRecordService schedulingApplyTransferClassRecordService;
    @Autowired
    private SchedulingAttendanceDailyService schedulingAttendanceDailyService;
    @Autowired
    private SchedulingLeaveBalanceService schedulingLeaveBalanceService;


    /**
     * 按月份分组
     *
     * @param params
     * @return
     */
    @Override
    public List<SchedulingApplyRecordStatisticsVo> countRecordGroupByTime(Map<String, Object> params) {
        return schedulingApplyRecordDao.countRecordGroupByTime(params);
    }

    @Override
    @Lock(lockExpresses = {
            // 锁定申请记录
            RedisLockConstants.SCHEDULING_APPLY_RECORD_LOCK + "#{#id}"
    })
    @Transactional(rollbackFor = RuntimeException.class)
    public void cancel(Long id) {
        SchedulingApplyRecordDto schedulingApplyRecordDto = selectById(id);
        if (schedulingApplyRecordDto == null) {
            return;
        }
        DeptPersonDto deptPersonDto = deptPersonService.selectCacheById(schedulingApplyRecordDto.getApplyDeptPersonId());
        if (deptPersonDto == null) {
            return;
        }
        Date date = new Date();
        SchedulingApplyRecordDto dto = new SchedulingApplyRecordDto();
        dto.setId(schedulingApplyRecordDto.getId());
        dto.setStatus(SchedulingApplyConstants.CANCEL_STATUS);
        dto.setStatusChangeTime(date);
        dto.setCurrentAtSeq(SchedulingApplyConstants.CURRENT_AT_SEQ_OF_CLOSE);
        dto.setEditor(deptPersonDto.getName());
        dto.setEditTime(date);

        SchedulingApplyRecordService schedulingApplyRecordService = SpringUtils.getBean(SchedulingApplyRecordService.class);
        schedulingApplyRecordService.updateById(dto);

        SchedulingApplyRecordAuditLogDto newlyLog = new SchedulingApplyRecordAuditLogDto();
        newlyLog.setStatus(SchedulingApplyConstants.CANCEL_STATUS);
        newlyLog.setAuditTime(date);
        //标记为初始节点操作
        newlyLog.setCurrentAtSeq(SchedulingApplyConstants.CURRENT_AT_SEQ_OF_CREATOR);
        newlyLog.setAuditDeptPersonId(deptPersonDto.getId());
        newlyLog.setRemark("");
        newlyLog.setApplyRecordId(dto.getId());
        schedulingApplyRecordAuditLogService.save(newlyLog);

        //回退假期余额
        if(SchedulingApplyConstants.ApplyTypeEnum.APPLY_LEAVE.getType().equals(schedulingApplyRecordDto.getApplyType())){
            //回退余额
            schedulingLeaveBalanceService.rollBackBalance(
                    schedulingApplyRecordDto.getApplyDeptPersonId(),
                    SchedulingApplyConstants.BalanceTypeEnum.APPLY.getType(),
                    schedulingApplyRecordDto.getId()
            );
        }
    }

    @Override
    @Lock(lockExpresses = {
            // 锁定申请记录
            RedisLockConstants.SCHEDULING_APPLY_RECORD_LOCK + "#{#recordId}"
    })
    @Transactional(rollbackFor = RuntimeException.class)
    public SchedulingApplyAuditRsp audit(Long recordId, Long personId, Byte status, String remark) {
        if (!status.equals(SchedulingApplyConstants.APPROVED_STATUS) && !status.equals(SchedulingApplyConstants.REJECT_STATUS)) {
            throw new SchedulingApplyAuditException("审核状态不合法");
        }

        SchedulingApplyRecordService schedulingApplyRecordService = SpringUtils.getBean(SchedulingApplyRecordService.class);

        SchedulingApplyRecordDto schedulingApplyRecordDto = schedulingApplyRecordService.selectById(recordId);
        if (schedulingApplyRecordDto == null) {
            throw new SchedulingApplyAuditException("审批记录不存在");
        }
        DeptPersonDto deptPersonDto = deptPersonService.selectCacheById(personId);
        if (deptPersonDto == null || SystemConstants.ABNORMAL.equals(deptPersonDto.getStatus())) {
            throw new SchedulingApplyAuditException("审核人员不存在或被禁用");
        }
        //查询审核人员
        List<SchedulingApplyRecordAuditPersonDto> schedulingApplyRecordAuditPersonDtos = schedulingApplyRecordAuditPersonService.selectByRecordId(recordId);
        SchedulingApplyRecordAuditPersonDto schedulingApplyRecordAuditPersonDto = schedulingApplyRecordAuditPersonDtos.stream()
                .filter(auditPersonDto ->
                        auditPersonDto.getApplyRecordId().equals(recordId)
                                && auditPersonDto.getAuditDeptPersonId().equals(personId)
                                && auditPersonDto.getCurrentAtSeq().equals(schedulingApplyRecordDto.getCurrentAtSeq())).findFirst().orElse(null);
        if (schedulingApplyRecordAuditPersonDto == null) {
            throw new SchedulingApplyAuditException("该员工没有审核权限");
        }
        Integer maxSeq = schedulingApplyRecordAuditPersonDtos.stream().max(Comparator.comparing(SchedulingApplyRecordAuditPersonDto::getCurrentAtSeq)).map(SchedulingApplyRecordAuditPersonDto::getCurrentAtSeq).orElse(null);
        if (maxSeq == null) {
            throw new SchedulingApplyAuditException("审批配置有误");
        }


        //提交审批结果
        SchedulingApplyRecordDto dto = new SchedulingApplyRecordDto();
        dto.setId(schedulingApplyRecordDto.getId());
        Date date = new Date();
        dto.setStatusChangeTime(date);
        dto.setEditor(deptPersonDto.getName());
        dto.setEditTime(date);

        //如果同意 看是否进入下一个节点
        if (status.equals(SchedulingApplyConstants.APPROVED_STATUS)) {
            //如果小于最大节点 则需要向下进行
            if (schedulingApplyRecordDto.getCurrentAtSeq().compareTo(maxSeq) < 0) {
                Integer currentAtSeq = schedulingApplyRecordDto.getCurrentAtSeq();
                dto.setCurrentAtSeq(++currentAtSeq);
            } else {
                //结束
                dto.setStatus(status);
                dto.setCurrentAtSeq(SchedulingApplyConstants.CURRENT_AT_SEQ_OF_CLOSE);
            }
            schedulingApplyRecordService.updateById(dto);
        } else {
            //结束
            dto.setStatus(status);
            dto.setCurrentAtSeq(SchedulingApplyConstants.CURRENT_AT_SEQ_OF_CLOSE);
            schedulingApplyRecordService.updateById(dto);

            //回退假期余额
            if(SchedulingApplyConstants.ApplyTypeEnum.APPLY_LEAVE.getType().equals(schedulingApplyRecordDto.getApplyType())){
                //回退余额
                schedulingLeaveBalanceService.rollBackBalance(
                        schedulingApplyRecordDto.getApplyDeptPersonId(),
                        SchedulingApplyConstants.BalanceTypeEnum.APPLY.getType(),
                        schedulingApplyRecordDto.getId()
                );
            }

        }


        // 1.留存日志
        SchedulingApplyRecordAuditLogDto newlyLog = new SchedulingApplyRecordAuditLogDto();
        newlyLog.setStatus(status);
        newlyLog.setAuditTime(date);
        //保存审核节点
        newlyLog.setCurrentAtSeq(schedulingApplyRecordDto.getCurrentAtSeq());
        newlyLog.setAuditDeptPersonId(deptPersonDto.getId());
        newlyLog.setRemark(remark);
        newlyLog.setApplyRecordId(recordId);
        schedulingApplyRecordAuditLogService.save(newlyLog);

        //同意后的处理
        if (SchedulingApplyConstants.CURRENT_AT_SEQ_OF_CLOSE.equals(dto.getCurrentAtSeq())) {
            //异步处理
            CompletableFuture.runAsync(() -> schedulingApplyRecordService.handleApproveRecord(schedulingApplyRecordDto.getId()));
        }

        SchedulingApplyAuditRsp schedulingApplyAuditRsp = new SchedulingApplyAuditRsp();
        schedulingApplyAuditRsp.setResult(SystemConstants.YES);
        schedulingApplyAuditRsp.setNextSeq(dto.getCurrentAtSeq());

        return schedulingApplyAuditRsp;
    }

    @Lock(lockExpresses = {
            // 锁定申请记录
            RedisLockConstants.SCHEDULING_APPLY_RECORD_LOCK + "#{#recordId}",
    })
    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public void handleResumptionRecord(Long recordId) {
        SchedulingApplyRecordService schedulingApplyRecordService = SpringUtils.getBean(SchedulingApplyRecordService.class);

        //将原始记录修改为已销假
        SchedulingApplyRecordDto applyRecordDto = selectById(recordId);
        //更新为销假状态
        applyRecordDto.setStatus(SchedulingApplyConstants.WRITE_OFF_STATUS);
        applyRecordDto.setStatusChangeTime(new Date());
        schedulingApplyRecordService.updateById(applyRecordDto);

        //现在只能销三种
        if (SchedulingApplyConstants.ApplyTypeEnum.APPLY_LEAVE.getType().equals(applyRecordDto.getApplyType())) {
            Map<String, Object> params = new HashMap<>();
            params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getTenantId), applyRecordDto.getTenantId());
            params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getVillageId), applyRecordDto.getVillageId());
            params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getApplyRecordId), applyRecordDto.getId());
            params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getApplyDeptPersonId), applyRecordDto.getApplyDeptPersonId());

            List<SchedulingApplyLeaveRecordDto> schedulingApplyLeaveRecordDtos = schedulingApplyLeaveRecordService.queryRecordList(params);

            if (CollectionUtils.isNotEmpty(schedulingApplyLeaveRecordDtos)) {
                //修改状态
                schedulingApplyLeaveRecordDtos.forEach(schedulingApplyLeaveRecordDto -> {
                    SchedulingApplyLeaveRecordDto leaveRecordDto = new SchedulingApplyLeaveRecordDto();
                    leaveRecordDto.setId(schedulingApplyLeaveRecordDto.getId());
                    leaveRecordDto.setStatus(SchedulingApplyConstants.WRITE_OFF_STATUS);
                    schedulingApplyLeaveRecordService.updateById(leaveRecordDto);

                    //通知重新处理考勤
                    RevokeReplayReqDto revokeReplayReqDto = new RevokeReplayReqDto();
                    revokeReplayReqDto.setApplyId(schedulingApplyLeaveRecordDto.getId());
                    revokeReplayReqDto.setApplyType(SchedulingApplyConstants.ApplyTypeEnum.APPLY_LEAVE.getType());
                    revokeReplayReqDto.setPersonId(schedulingApplyLeaveRecordDto.getApplyDeptPersonId());
                    schedulingAttendanceDailyService.revokeReplay(revokeReplayReqDto);
                });
            }
        }
        if (SchedulingApplyConstants.ApplyTypeEnum.OUT.getType().equals(applyRecordDto.getApplyType())) {
            Map<String, Object> params = new HashMap<>();
            params.put(LambdaUtil.getFieldName(SchedulingApplyOutRecordDto::getTenantId), applyRecordDto.getTenantId());
            params.put(LambdaUtil.getFieldName(SchedulingApplyOutRecordDto::getVillageId), applyRecordDto.getVillageId());
            params.put(LambdaUtil.getFieldName(SchedulingApplyOutRecordDto::getApplyRecordId), applyRecordDto.getId());
            params.put(LambdaUtil.getFieldName(SchedulingApplyOutRecordDto::getApplyDeptPersonId), applyRecordDto.getApplyDeptPersonId());

            List<SchedulingApplyOutRecordDto> schedulingApplyOutRecordDtos = schedulingApplyOutRecordService.queryRecordList(params);

            if (CollectionUtils.isNotEmpty(schedulingApplyOutRecordDtos)) {
                //修改状态
                schedulingApplyOutRecordDtos.forEach(outRecordDto -> {
                    SchedulingApplyOutRecordDto schedulingApplyOutRecordDto = new SchedulingApplyOutRecordDto();
                    schedulingApplyOutRecordDto.setId(outRecordDto.getId());
                    schedulingApplyOutRecordDto.setStatus(SchedulingApplyConstants.WRITE_OFF_STATUS);
                    schedulingApplyOutRecordService.updateById(schedulingApplyOutRecordDto);

                    //通知重新处理考勤
                    RevokeReplayReqDto revokeReplayReqDto = new RevokeReplayReqDto();
                    revokeReplayReqDto.setApplyId(outRecordDto.getId());
                    revokeReplayReqDto.setApplyType(SchedulingApplyConstants.ApplyTypeEnum.OUT.getType());
                    revokeReplayReqDto.setPersonId(outRecordDto.getApplyDeptPersonId());
                    schedulingAttendanceDailyService.revokeReplay(revokeReplayReqDto);
                });
            }
        }
        if (SchedulingApplyConstants.ApplyTypeEnum.TRIP.getType().equals(applyRecordDto.getApplyType())) {
            Map<String, Object> params = new HashMap<>();
            params.put(LambdaUtil.getFieldName(SchedulingApplyTripRecordDto::getTenantId), applyRecordDto.getTenantId());
            params.put(LambdaUtil.getFieldName(SchedulingApplyTripRecordDto::getVillageId), applyRecordDto.getVillageId());
            params.put(LambdaUtil.getFieldName(SchedulingApplyTripRecordDto::getApplyRecordId), applyRecordDto.getId());
            params.put(LambdaUtil.getFieldName(SchedulingApplyTripRecordDto::getApplyDeptPersonId), applyRecordDto.getApplyDeptPersonId());

            List<SchedulingApplyTripRecordDto> schedulingApplyTripRecordDtos = schedulingApplyTripRecordService.queryRecordList(params);

            if (CollectionUtils.isNotEmpty(schedulingApplyTripRecordDtos)) {
                //修改状态
                schedulingApplyTripRecordDtos.forEach(tripRecordDto -> {
                    SchedulingApplyTripRecordDto schedulingApplyTripRecordDtoOutRecordDto = new SchedulingApplyTripRecordDto();
                    schedulingApplyTripRecordDtoOutRecordDto.setId(tripRecordDto.getId());
                    schedulingApplyTripRecordDtoOutRecordDto.setStatus(SchedulingApplyConstants.WRITE_OFF_STATUS);
                    schedulingApplyTripRecordService.updateById(schedulingApplyTripRecordDtoOutRecordDto);

                    //通知重新处理考勤
                    RevokeReplayReqDto revokeReplayReqDto = new RevokeReplayReqDto();
                    revokeReplayReqDto.setApplyId(tripRecordDto.getId());
                    revokeReplayReqDto.setApplyType(SchedulingApplyConstants.ApplyTypeEnum.TRIP.getType());
                    revokeReplayReqDto.setPersonId(tripRecordDto.getApplyDeptPersonId());
                    schedulingAttendanceDailyService.revokeReplay(revokeReplayReqDto);
                });
            }
        }
    }

    @Lock(lockExpresses = {
            // 锁定申请记录
            RedisLockConstants.SCHEDULING_APPLY_RECORD_LOCK + "#{#recordId}",
    })
    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public void handleApproveRecord(Long recordId) {
        SchedulingApplyRecordDto applyRecordDto = selectById(recordId);
        if (!SchedulingApplyConstants.CURRENT_AT_SEQ_OF_CLOSE.equals(applyRecordDto.getCurrentAtSeq())) {
            return;
        }
        SchedulingApplyRecordService schedulingApplyRecordService = SpringUtils.getBean(SchedulingApplyRecordService.class);

        if (SchedulingApplyConstants.ApplyTypeEnum.APPLY_LEAVE.getType().equals(applyRecordDto.getApplyType())) {

            SchedulingApplyRecordDto.LeaveFormData leaveFormData = JacksonUtils.parse(applyRecordDto.getFormDataJson(), SchedulingApplyRecordDto.LeaveFormData.class);

            SchedulingApplyLeaveRecordDto leaveRecordDto = new SchedulingApplyLeaveRecordDto();
            leaveRecordDto.setTenantId(applyRecordDto.getTenantId());
            leaveRecordDto.setVillageId(applyRecordDto.getVillageId());
            leaveRecordDto.setApplyRecordId(applyRecordDto.getId());
            leaveRecordDto.setApplyDeptPersonId(applyRecordDto.getApplyDeptPersonId());
            leaveRecordDto.setLeaveId(leaveFormData.getLeaveId());
            leaveRecordDto.setLeaveType(leaveFormData.getType());
            leaveRecordDto.setStartTime(DateUtils.parse(leaveFormData.getStartTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT));
            leaveRecordDto.setCalCursorTime(DateUtils.parse(leaveFormData.getStartTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT));
            leaveRecordDto.setEndTime(DateUtils.parse(leaveFormData.getEndTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT));
            leaveRecordDto.setTimeLength(leaveFormData.getTimeLength());
            leaveRecordDto.setTimeUnit(leaveFormData.getTimeUnit());
            leaveRecordDto.setBalanceDetailJson(JacksonUtils.toJsonString(leaveFormData.getBalanceDetail()));
            leaveRecordDto.setHistoryBalance(leaveFormData.getHistoryBalance());
            leaveRecordDto.setHistoryBalanceUnit(leaveFormData.getHistoryBalanceUnit());
            leaveRecordDto.setStatus(SchedulingApplyConstants.APPROVED_STATUS);

            leaveRecordDto.setId(schedulingApplyLeaveRecordService.save(leaveRecordDto));


            SchedulingAttendanceDailyApplyDto schedulingAttendanceDailyApplyDto = new SchedulingAttendanceDailyApplyDto();
            schedulingAttendanceDailyApplyDto.setApplyType(SchedulingApplyConstants.ApplyTypeEnum.APPLY_LEAVE.getType());
            schedulingAttendanceDailyApplyDto.setApplyId(leaveRecordDto.getId());
            schedulingAttendanceDailyApplyDto.setPersonId(leaveRecordDto.getApplyDeptPersonId());
            schedulingAttendanceDailyService.applyEffect(schedulingAttendanceDailyApplyDto);


        } else if (SchedulingApplyConstants.ApplyTypeEnum.RESUMPTION_LEAVE.getType().equals(applyRecordDto.getApplyType())) {

            SchedulingApplyRecordDto.ResumptionLeaveFormData resumptionLeaveFormData = JacksonUtils.parse(applyRecordDto.getFormDataJson(), SchedulingApplyRecordDto.ResumptionLeaveFormData.class);

            SchedulingApplyResumptionLeaveRecordDto resumptionLeaveRecordDto = new SchedulingApplyResumptionLeaveRecordDto();
            resumptionLeaveRecordDto.setTenantId(applyRecordDto.getTenantId());
            resumptionLeaveRecordDto.setVillageId(applyRecordDto.getVillageId());
            resumptionLeaveRecordDto.setApplyRecordId(applyRecordDto.getId());
            resumptionLeaveRecordDto.setApplyDeptPersonId(applyRecordDto.getApplyDeptPersonId());
            resumptionLeaveRecordDto.setOriginalRecordId(resumptionLeaveFormData.getRecordId());
            resumptionLeaveRecordDto.setStatus(SchedulingApplyConstants.APPROVED_STATUS);


            resumptionLeaveRecordDto.setId(schedulingApplyResumptionLeaveRecordService.save(resumptionLeaveRecordDto));
            //TODO 销假处理
            schedulingApplyRecordService.handleResumptionRecord(resumptionLeaveRecordDto.getApplyRecordId());

        } else if (SchedulingApplyConstants.ApplyTypeEnum.RENEW_CARD.getType().equals(applyRecordDto.getApplyType())) {

            SchedulingApplyRecordDto.RenewCardFormData renewCardFormData = JacksonUtils.parse(applyRecordDto.getFormDataJson(), SchedulingApplyRecordDto.RenewCardFormData.class);

            SchedulingApplyRenewCardRecordDto renewCardRecordDto = new SchedulingApplyRenewCardRecordDto();
            renewCardRecordDto.setTenantId(applyRecordDto.getTenantId());
            renewCardRecordDto.setVillageId(applyRecordDto.getVillageId());
            renewCardRecordDto.setApplyRecordId(applyRecordDto.getId());
            renewCardRecordDto.setApplyDeptPersonId(applyRecordDto.getApplyDeptPersonId());
            renewCardRecordDto.setRenewCardTime(DateUtils.parse(renewCardFormData.getRenewCardTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT));
            renewCardRecordDto.setStatus(SchedulingApplyConstants.APPROVED_STATUS);

            renewCardRecordDto.setId(schedulingApplyRenewCardRecordService.save(renewCardRecordDto));

            SchedulingAttendanceDailyApplyDto schedulingAttendanceDailyApplyDto = new SchedulingAttendanceDailyApplyDto();
            schedulingAttendanceDailyApplyDto.setApplyType(SchedulingApplyConstants.ApplyTypeEnum.RENEW_CARD.getType());
            schedulingAttendanceDailyApplyDto.setApplyId(renewCardRecordDto.getId());
            schedulingAttendanceDailyApplyDto.setPersonId(renewCardRecordDto.getApplyDeptPersonId());
            schedulingAttendanceDailyService.applyEffect(schedulingAttendanceDailyApplyDto);

        } else if (SchedulingApplyConstants.ApplyTypeEnum.TRIP.getType().equals(applyRecordDto.getApplyType())) {

            SchedulingApplyRecordDto.TripFormData tripFormData = JacksonUtils.parse(applyRecordDto.getFormDataJson(), SchedulingApplyRecordDto.TripFormData.class);

            if (CollectionUtils.isNotEmpty(tripFormData.getTripDataList())) {
                tripFormData.getTripDataList().forEach(tripData -> {
                    SchedulingApplyTripRecordDto tripRecordDto = new SchedulingApplyTripRecordDto();
                    tripRecordDto.setTenantId(applyRecordDto.getTenantId());
                    tripRecordDto.setVillageId(applyRecordDto.getVillageId());
                    tripRecordDto.setApplyRecordId(applyRecordDto.getId());
                    tripRecordDto.setApplyDeptPersonId(applyRecordDto.getApplyDeptPersonId());
                    tripRecordDto.setSeq(tripData.getSeq());
                    tripRecordDto.setStartCityName(tripData.getStartCityName());
                    tripRecordDto.setStartCityCode(tripData.getStartCityCode());
                    tripRecordDto.setEndCityName(tripData.getEndCityName());
                    tripRecordDto.setEndCityCode(tripData.getEndCityCode());
                    tripRecordDto.setStartTime(DateUtils.parse(tripData.getStartTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT));
                    tripRecordDto.setCalCursorTime(DateUtils.parse(tripData.getStartTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT));
                    tripRecordDto.setEndTime(DateUtils.parse(tripData.getEndTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT));
                    tripRecordDto.setTimeLength(tripData.getTimeLength());
                    tripRecordDto.setTimeUnit(tripData.getTimeUnit());
                    tripRecordDto.setTransportation(tripData.getTransportation());
                    tripRecordDto.setTripType(tripData.getTripType());
                    tripRecordDto.setStatus(SchedulingApplyConstants.APPROVED_STATUS);
                    tripRecordDto.setId(schedulingApplyTripRecordService.save(tripRecordDto));

                    SchedulingAttendanceDailyApplyDto schedulingAttendanceDailyApplyDto = new SchedulingAttendanceDailyApplyDto();
                    schedulingAttendanceDailyApplyDto.setApplyType(SchedulingApplyConstants.ApplyTypeEnum.TRIP.getType());
                    schedulingAttendanceDailyApplyDto.setApplyId(tripRecordDto.getId());
                    schedulingAttendanceDailyApplyDto.setPersonId(tripRecordDto.getApplyDeptPersonId());
                    schedulingAttendanceDailyService.applyEffect(schedulingAttendanceDailyApplyDto);
                });
            }

        } else if (SchedulingApplyConstants.ApplyTypeEnum.OUT.getType().equals(applyRecordDto.getApplyType())) {

            SchedulingApplyRecordDto.OutFormData outFormData = JacksonUtils.parse(applyRecordDto.getFormDataJson(), SchedulingApplyRecordDto.OutFormData.class);

            SchedulingApplyOutRecordDto outRecordDto = new SchedulingApplyOutRecordDto();
            outRecordDto.setTenantId(applyRecordDto.getTenantId());
            outRecordDto.setVillageId(applyRecordDto.getVillageId());
            outRecordDto.setApplyRecordId(applyRecordDto.getId());
            outRecordDto.setApplyDeptPersonId(applyRecordDto.getApplyDeptPersonId());
            outRecordDto.setDestination(outFormData.getDestination());
            outRecordDto.setStartTime(DateUtils.parse(outFormData.getStartTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT));
            outRecordDto.setCalCursorTime(DateUtils.parse(outFormData.getStartTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT));
            outRecordDto.setEndTime(DateUtils.parse(outFormData.getEndTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT));
            outRecordDto.setTimeLength(outFormData.getTimeLength());
            outRecordDto.setTimeUnit(outFormData.getTimeUnit());
            outRecordDto.setStatus(SchedulingApplyConstants.APPROVED_STATUS);

            outRecordDto.setId(schedulingApplyOutRecordService.save(outRecordDto));

            SchedulingAttendanceDailyApplyDto schedulingAttendanceDailyApplyDto = new SchedulingAttendanceDailyApplyDto();
            schedulingAttendanceDailyApplyDto.setApplyType(SchedulingApplyConstants.ApplyTypeEnum.OUT.getType());
            schedulingAttendanceDailyApplyDto.setApplyId(outRecordDto.getId());
            schedulingAttendanceDailyApplyDto.setPersonId(outRecordDto.getApplyDeptPersonId());
            schedulingAttendanceDailyService.applyEffect(schedulingAttendanceDailyApplyDto);


        } else if (SchedulingApplyConstants.ApplyTypeEnum.OVER_TIME.getType().equals(applyRecordDto.getApplyType())) {

            SchedulingApplyRecordDto.OvertimeFormData overtimeFormData = JacksonUtils.parse(applyRecordDto.getFormDataJson(), SchedulingApplyRecordDto.OvertimeFormData.class);

            SchedulingApplyOvertimeRecordDto overtimeRecordDto = new SchedulingApplyOvertimeRecordDto();
            overtimeRecordDto.setTenantId(applyRecordDto.getTenantId());
            overtimeRecordDto.setVillageId(applyRecordDto.getVillageId());
            overtimeRecordDto.setApplyRecordId(applyRecordDto.getId());
            overtimeRecordDto.setApplyDeptPersonId(applyRecordDto.getApplyDeptPersonId());
            overtimeRecordDto.setStartTime(DateUtils.parse(overtimeFormData.getStartTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT));
            overtimeRecordDto.setCalCursorTime(DateUtils.parse(overtimeFormData.getStartTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT));
            overtimeRecordDto.setEndTime(DateUtils.parse(overtimeFormData.getEndTime(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT));
            overtimeRecordDto.setTimeLength(overtimeRecordDto.getTimeLength());
            overtimeRecordDto.setTimeUnit(overtimeRecordDto.getTimeUnit());
            overtimeRecordDto.setStatus(SchedulingApplyConstants.APPROVED_STATUS);

            overtimeRecordDto.setId(schedulingApplyOvertimeRecordService.save(overtimeRecordDto));


        } else if (SchedulingApplyConstants.ApplyTypeEnum.TRANSFER_CLASS.getType().equals(applyRecordDto.getApplyType())) {

            SchedulingApplyRecordDto.TransferClassFormData transferClassFormData = JacksonUtils.parse(applyRecordDto.getFormDataJson(), SchedulingApplyRecordDto.TransferClassFormData.class);

            SchedulingApplyTransferClassRecordDto classRecordDto = new SchedulingApplyTransferClassRecordDto();
            classRecordDto.setTenantId(applyRecordDto.getTenantId());
            classRecordDto.setVillageId(applyRecordDto.getVillageId());
            classRecordDto.setApplyRecordId(applyRecordDto.getId());
            classRecordDto.setApplyDeptPersonId(applyRecordDto.getApplyDeptPersonId());
            classRecordDto.setSwitchType(transferClassFormData.getType());
            classRecordDto.setSwitchPeronId(transferClassFormData.getDeptPersonId());
            classRecordDto.setSwitchTime(DateUtils.parse(transferClassFormData.getDate(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT));
            classRecordDto.setReturnTime(DateUtils.parse(transferClassFormData.getReturnDate(), DateUtils.FormatType.NORM_DATETIME_MINUTE_FORMAT));
            classRecordDto.setStatus(SchedulingApplyConstants.APPROVED_STATUS);

            schedulingApplyTransferClassRecordService.save(classRecordDto);
        }
    }


    @Override
    public int queryRecordTotal(Map<String, Object> params) {
        return schedulingApplyRecordDao.queryRecordTotal(params);
    }

    @Override
    public List<SchedulingApplyRecordDto> queryRecordList(Map<String, Object> params) {
        List<SchedulingApplyRecordEntity> list = schedulingApplyRecordDao.queryRecordList(params);
        List<SchedulingApplyRecordDto> result = CopyUtils.copyList(list, SchedulingApplyRecordDto.class);
        result.forEach(this::paddingField);
        return result;
    }

    @Override
    public List<SchedulingApplyCountVO> applyRecordAuditCount(Map<String, Object> params) {
        return schedulingApplyRecordDao.applyRecordAuditCount(params);
    }

    @Override
    public List<SchedulingApplyCountVO> applyRecordCount(Map<String, Object> params) {
        return schedulingApplyRecordDao.applyRecordCount(params);
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public Long submitRecord(SchedulingApplyRecordDto dto) {
        SchedulingApplyRecordService schedulingApplyRecordService = SpringUtils.getBean(SchedulingApplyRecordService.class);
        if (dto.getId() != null) {
            schedulingApplyRecordService.updateById(dto);
        } else {
            dto.setId(schedulingApplyRecordService.save(dto));
        }
        //如果是请假 则立即扣减假期余额
        if(SchedulingApplyConstants.ApplyTypeEnum.APPLY_LEAVE.getType().equals(dto.getApplyType())){
            SchedulingApplyRecordDto.LeaveFormData leaveFormData = JacksonUtils.parse(dto.getFormDataJson(), SchedulingApplyRecordDto.LeaveFormData.class);
            //更新余额
            if (leaveFormData.getBalanceDetail() != null && CollectionUtils.isNotEmpty(leaveFormData.getBalanceDetail().getBalanceTimes())) {
                for (SchedulingBalanceTimeVO schedulingBalanceTimeVO : leaveFormData.getBalanceDetail().getBalanceTimes()) {
                    schedulingLeaveBalanceService.updateBalance(
                            dto.getApplyDeptPersonId(),
                            SchedulingApplyConstants.BalanceTypeEnum.APPLY.getType(),
                            dto.getId(),
                            schedulingBalanceTimeVO.getBalanceId(),
                            schedulingBalanceTimeVO.getAppliedBalance(),
                            schedulingBalanceTimeVO.getUnit()
                    );
                }
            }

        }

        schedulingApplyRecordService.initAuditSeq(dto);
        return dto.getId();
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void initAuditSeq(SchedulingApplyRecordDto dto) {

        //1.查询审批流
        SchedulingAuditPipelineDto auditPipelineDto = schedulingAuditPipelineService.selectByVillageAndType(dto.getVillageId()
                , dto.getApplyType());
        List<SchedulingAuditPipelineSeqDto> seqs = auditPipelineDto == null
                ? new ArrayList<>()
                : auditPipelineDto.getAuditPipelineSeqDtos();
        //2.初始化节点
        Integer nextSeqNumber = SchedulingApplyConstants.CURRENT_AT_SEQ_OF_BEGIN;
        //3.实际审批节点序号
        Integer sqeNum = SchedulingApplyConstants.CURRENT_AT_SEQ_OF_BEGIN;
        while (true) {

            SchedulingAuditPipelineSeqDto nextSeqDto = null;
            //5.1如果是调班申请 并且是与他人调班,需要将这个人加到第一个节点
            if (SchedulingApplyConstants.ApplyTypeEnum.TRANSFER_CLASS.getType().equals(dto.getApplyType())) {
                SchedulingApplyRecordDto.TransferClassFormData transferClassFormData = JacksonUtils.parse(dto.getFormDataJson(), SchedulingApplyRecordDto.TransferClassFormData.class);
                if (SchedulingApplyConstants.TransferClassTypeEnum.OTHER.getType().equals(transferClassFormData.getType())) {
                    SchedulingApplyRecordAuditPersonDto schedulingApplyRecordAuditPersonDto = new SchedulingApplyRecordAuditPersonDto();
                    schedulingApplyRecordAuditPersonDto.setAuditDeptPersonId(dto.getApplyDeptPersonId());
                    schedulingApplyRecordAuditPersonDto.setCurrentAtSeq(sqeNum);
                    schedulingApplyRecordAuditPersonDto.setApplyRecordId(dto.getId());
                    schedulingApplyRecordAuditPersonService.save(schedulingApplyRecordAuditPersonDto);
                    //只增加实际审批节点序号
                    sqeNum++;
                    continue;
                }
            }

            final Integer finalNextSqeNum = nextSeqNumber;
            final Integer finalSqeNum = sqeNum;
            Optional<SchedulingAuditPipelineSeqDto> nextItem = seqs.stream().filter(o -> finalNextSqeNum.equals(o.getSeq()))
                    .findFirst();
            if (nextItem.isPresent()) {
                nextSeqDto = nextItem.get();
            }
            //5.2查询审批节点人员
            if (nextSeqDto != null) {
                List<Long> personIds;

                if (SchedulingAuditPipelineSeqConstants.AUDIT_TYPE_DEPARTMENT_DESIGNATED_PERSON
                        .equals(nextSeqDto.getAuditType())) {
                    List<SchedulingAuditPipelineSeqDeptPersonDto> personDtos = schedulingAuditPipelineSeqDeptPersonService
                            .selectBySeqId(nextSeqDto.getId());
                    personIds = personDtos.stream()
                            .map(SchedulingAuditPipelineSeqDeptPersonDto::getDeptPersonId)
                            .collect(Collectors.toList());
                } else {
                    List<DeptPersonDto> personDtos = null;
                    if (nextSeqDto.getDeptId() == null || nextSeqDto.getDeptId().equals(-1L)) {
                        DeptPersonDto deptPersonDto = deptPersonService.selectCacheById(dto.getApplyDeptPersonId());
                        personDtos = deptPersonService.selectByDeptId(deptPersonDto.getDeptId());
                    } else {
                        personDtos = deptPersonService.selectByDeptId(nextSeqDto.getDeptId());
                    }
                    final Byte finalNextAuditType = nextSeqDto.getAuditType();
                    personIds = personDtos.stream()
                            .filter(o -> SchedulingAuditPipelineSeqConstants.AUDIT_TYPE_ALL_PERSONNEL_OF_THE_DEPARTMENT.equals(
                                    finalNextAuditType) || DeptPersonConstants.DEPT_ADMIN.equals(o.getIsAdmin()))
                            .map(DeptPersonDto::getId)
                            .collect(Collectors.toList());
                }
                if (personIds.isEmpty()) {
                    throw new CheckErrorException("审批流下一序列没有审批人");
                }
                List<SchedulingApplyRecordAuditPersonDto> nextAuditPersonList = personIds.stream().map(id -> {
                    SchedulingApplyRecordAuditPersonDto schedulingApplyRecordAuditPersonDto = new SchedulingApplyRecordAuditPersonDto();
                    schedulingApplyRecordAuditPersonDto.setCurrentAtSeq(finalSqeNum);
                    schedulingApplyRecordAuditPersonDto.setAuditDeptPersonId(id);
                    schedulingApplyRecordAuditPersonDto.setApplyRecordId(dto.getId());
                    return schedulingApplyRecordAuditPersonDto;
                }).collect(Collectors.toList());
                schedulingApplyRecordAuditPersonService.saveBatch(nextAuditPersonList);
                //序号进位
                nextSeqNumber++;
                sqeNum++;
            } else {
                break;
            }
        }
    }


    @Override
    public SchedulingApplyRecordDto selectCacheById(Long id) {
        return CacheKit.cacheToRedis(
                () -> selectById(id),
                SCHEDULING_APPLY_RECORD_CACHE_BY_ID_FUNC.apply(id),
                SystemConstants.NOT_NULL_CACHE_EXPIRE_SECONDS
        );
    }

    @Override
    public SchedulingApplyRecordDto selectById(Long id) {
        SchedulingApplyRecordEntity entity = schedulingApplyRecordDao.selectById(id);
        return paddingField(CopyUtils.copyObj(entity, SchedulingApplyRecordDto.class));
    }

    @Override
    public List<SchedulingApplyRecordDto> queryList(Map<String, Object> params) {
        List<SchedulingApplyRecordEntity> list = schedulingApplyRecordDao.queryList(params);
        List<SchedulingApplyRecordDto> result = CopyUtils.copyList(list, SchedulingApplyRecordDto.class);
        result.forEach(this::paddingField);
        return result;
    }

    @Override
    public List<SchedulingApplyRecordDto> queryListWithoutPadding(Map<String, Object> params) {
        List<SchedulingApplyRecordEntity> list = schedulingApplyRecordDao.queryList(params);
        return CopyUtils.copyList(list, SchedulingApplyRecordDto.class);
    }

    /**
     * 此方法慎用 禁止填充List一类 比如一个人有多个地址这里不允许填充
     * 填充的原则是: 1:被填充对象不经常变换 2:根据id可以走缓存 3数据足够简单 比如设备类型 所属园区等
     *
     * @param dto
     * @return
     */
    private SchedulingApplyRecordDto paddingField(SchedulingApplyRecordDto dto) {
        if (dto != null) {
            // 申请人信息
            DeptPersonDto deptPersonDto = deptPersonService.selectCacheById(dto.getApplyDeptPersonId());
            if (deptPersonDto != null) {
                dto.setApplyPersonName(deptPersonDto.getName());
                dto.setDeptName(deptPersonDto.getDept() != null ? deptPersonDto.getDept().getName() : "");
            }
            VillageDto villageDto = villageService.selectCacheById(dto.getVillageId());
            if (villageDto != null) {
                dto.setVillageName(villageDto.getName());
            }


        }
        return dto;
    }

    @Override
    public int queryTotal(Map<String, Object> params) {
        return schedulingApplyRecordDao.queryTotal(params);
    }

    @Override
    public Long save(SchedulingApplyRecordDto dto) {
        SchedulingApplyRecordEntity entity = CopyUtils.copyObj(dto, SchedulingApplyRecordEntity.class);
        entity.setId(Snowflake.nextId());
        schedulingApplyRecordDao.insert(entity);
        return entity.getId();
    }

    @Override
    @Lock(lockExpresses = {
            // 锁定申请记录
            RedisLockConstants.SCHEDULING_APPLY_RECORD_LOCK + "#{#dto.id}"
    })
    public void updateById(SchedulingApplyRecordDto dto) {
        SchedulingApplyRecordEntity entity = CopyUtils.copyObj(dto, SchedulingApplyRecordEntity.class);

        schedulingApplyRecordDao.updateById(entity);

        CLEAR_CACHE_FUNC.accept(dto);
    }

    @Override
    @Lock(lockExpresses = {
            // 锁定申请记录
            RedisLockConstants.SCHEDULING_APPLY_RECORD_LOCK + "#{#dto.id}"
    })
    public void updateAllColumnById(SchedulingApplyRecordDto dto) {
        SchedulingApplyRecordEntity entity = CopyUtils.copyObj(dto, SchedulingApplyRecordEntity.class);

        MyBatisPlusUtils.updateAllColumnById(entity, schedulingApplyRecordDao);

        CLEAR_CACHE_FUNC.accept(dto);
    }

    @Override
    public void deleteById(Long id) {

        CLEAR_CACHE_FUNC.accept(selectById(id));

        schedulingApplyRecordDao.deleteById(id);

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteBatch(Long[] ids) {
        for (Long id : ids) {
            deleteById(id);
        }
    }
}
